#include "Networking.h"
#include "Engine.h"
#include "../User Interface/Gui_Handler.h"

Networking::Networking()
	: m_server(NULL)
	, m_net(NULL)
	, m_iProgramType(0)
	, m_iPort(0)
	, m_iClientNum(-1)
	, m_bInitialized(false)
{}

int Networking::Init(int type, int listenPort, char* hostIP)
{
	m_iProgramType = type;
	if(m_iProgramType != NETWORK_CLIENT && m_iProgramType != NETWORK_HOST)
	{
		m_iProgramType = NETWORK_CLIENT;
		return NETWORK_ERR_INVALID_PROGTYPE;
	}
	m_iPort = listenPort;

	if(m_iProgramType == NETWORK_HOST)
	{
		// initialize the server module
		ServerModule* s = new ServerModule(m_iPort);
		m_server = s;
		s->setHandleClient(netCode, this);

		// if this program is the host, have the client connect to the home IP for convenience
		hostIP = "127.0.0.1";
		m_server->run();
	}

	// initialize the client to connect to the server (even if this program is the host)
	if(!hostIP)
		return NETWORK_ERR_HOST_NDEF;
	ClientModule* c = new ClientModule(hostIP, m_iPort);
	m_net = c;

	// send a message requesting this client's index
	m_net->getOutbox()->addLong(NETWORK_REQUEST_CLIENT_INDEX);
	m_net->getOutbox()->addLong(0);

	m_net->run();
	m_bInitialized = true;
	return NETWORK_SUCCESS;
}

bool Networking::netCode(ServerModule* pServer, int const & a_clientIndex, void* a_data)
{
	Networking* n = (Networking*)a_data;
	ClientModule* c = pServer->getClient(a_clientIndex);
	Vector<task> tasksForOtherClients;
	ByteBuffer* out;
	int code;
	int dataSize;
	bool ret;
	ret = n->netCode(c, tasksForOtherClients);

	// for every task to be sent out to other clients
	for(int i = 0; i < tasksForOtherClients.size(); ++i)
	{
		// go through each client
		int numClients = pServer->getClientCount();
		for(int j = 0; j < pServer->getClientCount(); ++j)
		{
			if(a_clientIndex != j)
			{
				if(pServer->getClient(j))
				{
					out = pServer->getClient(j)->getOutbox();
					code = tasksForOtherClients[i].code;
					dataSize = tasksForOtherClients[i].dataSize;

					// combined code from below
					ByteBuffer* data = (ByteBuffer*)tasksForOtherClients[i].data;
					out->addLong(code);
					out->addLong(data->size());
					out->addBytes(data->getRawList(), data->size());
				}
			}
		}
	}

	// delete data *after* it is no longer needed
	for(int i = 0; i < tasksForOtherClients.size(); ++i)
	{
		ByteBuffer* dataToDelete = (ByteBuffer*)tasksForOtherClients[i].data;
		delete dataToDelete;
		dataToDelete = NULL;
	}

	return ret;
}

bool Networking::netCode(ClientModule* c, Vector<task>& tasksForOtherClients)
{
	int index = 0;
	ByteBuffer* in	= c->getInbox();
	ByteBuffer* out = c->getOutbox();

	while(index < in->size())
	{
		int code = in->getLong(index);
		index += sizeof(long);
		int size = in->getLong(index);
		index+= sizeof(long);
		switch(code)
		{
		case NETWORK_REQUEST_CLIENT_INDEX:
			{
				out->addLong(NETWORK_REQUEST_CLIENT_INDEX);
				out->addLong(sizeof(long));
				out->addLong(c->getIndex());

				break;
			}
		case NETWORK_REQUEST_CHATBOX_ADD_MESSAGE:
			{
				ByteBuffer* messageData = new ByteBuffer;
				for(int i = index; i < index+size; ++i)
					messageData->addByte(in->getByte(i));
				index += messageData->size();

				// validate by checking for a null terminator at the end
				if(messageData->get(size-1) == '\0')
				{
					task t(NETWORK_CHATBOX_NEW_MESSAGE, messageData, size);
					tasksForOtherClients.push(t);
				}
				else
				{
					out->addLong(NETWORK_ERR_INVALID_DATA);
					out->addLong(NETWORK_NO_DATA);	// no data follows this response message
				}
				break;
			}

		case NETWORK_GIVE_PLAYER_INFO:
		case NETWORK_NEW_PLAYER_INFO:
		case NETWORK_SEND_ENTITY:
		case NETWORK_UPDATE_ENTITY:
		case NETWORK_UPDATE_ENEMY:
		case NETWORK_UPDATE_PLAYER_EQUIPS:
		case NETWORK_SEND_PROJECTILE:
		case NETWORK_UPDATE_ENTITY_HEALTH:
			{
				// just make a copy of the information and send it to be handled by other clients
				ByteBuffer* Data = new ByteBuffer;
				for(int i = index; i < size+index; ++i)
					Data->addByte(in->getByte(i));
				index += Data->size();
				
				task t(code, Data, sizeof(ByteBuffer*));
				tasksForOtherClients.push(t);
				break;
			}
		case NETWORK_REQUEST_WORLD_SEED_FILES:
			{
				char** files = WORLD_MODULE::Ins()->ConcatenateSaveInfo();
				int numFiles = WORLD_MODULE::Ins()->getNumSaveFiles();

				if(Engine::Ins()->game.getNumPlayers() >= MAX_PLAYERS)
				{
					out->addLong(code);
					out->addLong(NETWORK_NO_DATA);
				}
				else
				{
					out->addLong(code);
					// normally the number added here would be the byte size, but it doesn't make much sense to do that in this case
					// we don't need to know the byte size, because the receiving client will parse through each string until it hits the null terminator
					// the number of files will be the otherwise unknown variant for this case, so we give it that
					out->addLong(numFiles);

					// we also need to push in the MapW and MapH so the receiving client knows how to iterate when creating files
					out->addLong(WORLD_MODULE::Ins()->getMapW());
					out->addLong(WORLD_MODULE::Ins()->getMapH());

					for(int i = 0; i < numFiles; ++i)
						out->addBytes((unsigned char*)files[i], strLength(files[i])+1);
				}
				break;
			}
		case NETWORK_UPDATE_WORLD_BLOCK:
			{
				int wcData;
				int temp = 0;
				// create a new byte buffer to hold just this data because
				// the bytebuffer struct has no viable getBytes(index, size) method
				// also, we want to make a copy of the bytes anyway, 
				// because the inbox will be cleared after all messages are processed
				ByteBuffer* rawData = new ByteBuffer;
				for(int i = index; i < size+index; ++i)
					rawData->addByte(in->getByte(i));
				

				wcData = rawData->getLong(0);
				temp = rawData->getShort(sizeof(long));
				temp = rawData->getShort(sizeof(long)+sizeof(short));
				if(wcData >= 0)
				{
					task t(code, rawData, sizeof(ByteBuffer*));
					tasksForOtherClients.push(t);
				}
				else
				{
					out->addLong(NETWORK_ERR_INVALID_DATA);
					out->addLong(NETWORK_NO_DATA);	// no data follows this response message
				}

				index += size;
				break;
			}
		case NETWORK_CLIENT_DISCONNECT:
			{
				//this client has disconnected, simply give the message to everyone, including self
				ByteBuffer* data = new ByteBuffer;
				data->addShort((short)c->getIndex());
				task t(NETWORK_CLIENT_DISCONNECT, data, sizeof(ByteBuffer*));
				tasksForOtherClients.push(t);

				out->addLong(code);
				out->addLong(sizeof(short));
				out->addShort((short)c->getIndex());

				break;
			}
		}
	}

	in->clear();
	return true;
}

void Networking::Update()
{
	if(!m_bInitialized)
		return;

	if(m_server)
		m_server->run();

	int result = m_net->run();

	// if this client has been disconnected
	if(result == NetModule::DISCONNECTING)
	{
		GUI.setMessage("Host has become unresponsive.", GUI_Handler::CHATBOXOUTPUT);
		m_net->releaseSockets();
		WORLD_MODULE::Ins()->removePlayerEntities();

		m_net->getInbox()->addLong(NETWORK_ERR_HOST_DISCONNECTED);
		m_net->getInbox()->addLong(NETWORK_NO_DATA);
	}
}

void Networking::Release()
{
	if(m_server)
	{
		delete m_server;
		m_server = NULL;
	}

	if(m_net)
	{
		delete m_net;
		m_net = NULL;
	}

	if(outfile.is_open())
		outfile.close();

	m_bInitialized = false;
}
ByteBuffer* Networking::getServerMessages()
{
	if(!m_bInitialized)
		return NULL;

	ByteBuffer* ret = m_net->getInbox();
	return ret;
}

int Networking::MakeServerRequest(int code, void* data, int dataSize)
{
	if(!m_bInitialized)
		return NETWORK_ERR_OFFLINE;

	ByteBuffer* out = m_net->getOutbox();

	switch(code)
	{
	case NETWORK_REQUEST_CHATBOX_ADD_MESSAGE:
		{
			unsigned char* message = (unsigned char*)data;
			if(out)
			{
				out->addLong(code);
				out->addLong(dataSize);
				out->addBytes(message, dataSize);
			}

			delete message;
			break;
		}
	case NETWORK_GIVE_PLAYER_INFO:
	case NETWORK_NEW_PLAYER_INFO:
	case NETWORK_SEND_ENTITY:
		{
			// perform serialization on entity before attempting to send across network
			Entity* e = (Entity*)data;
			int index = 0;
			ByteBuffer* bufferData = new ByteBuffer;

			e->serialize(bufferData);
			dataSize = bufferData->size();

			if(out)
			{
				out->addLong(code);
				out->addLong(dataSize);
				out->addBytes(bufferData->getRawList(), dataSize);
			}

			delete bufferData;
			break;
		}	
	case NETWORK_UPDATE_ENTITY:
	case NETWORK_UPDATE_ENEMY:
		{
			MoveInfo* m = (MoveInfo*)data;
			int seriFloatSize = sizeof(long)+sizeof(short);
			dataSize = sizeof(short) + 5*seriFloatSize + sizeof(char);

			if(out)
			{
				bool valid = false;
				if(code == NETWORK_UPDATE_ENTITY)
				{
					if(m->index >= 0 && m->index < MAX_PLAYERS)
						valid = true;
				}
				else if(code == NETWORK_UPDATE_ENEMY)
					if(m->index >= 0 && m->index < WORLD_MODULE::Ins()->getNumEntities())
						valid = true;

				if(valid)
				{
					removeDuplicateData(out, data);
					out->addLong(code);
					out->addLong(dataSize);
					out->addShort(m->index);
					if(!m->bGravity)
						out->addByte('\0');
					else
						out->addByte('1');
					m->pos.serialize(out);
					m->p.serialize(out);
				}

			}
			break;
		}
	case NETWORK_REQUEST_WORLD_SEED_FILES:
		{
			out->addLong(code);
			out->addLong(NETWORK_NO_DATA);

			break;
		}
	case NETWORK_UPDATE_WORLD_BLOCK:
		{
			WorldChange* wc = (WorldChange*)data;
			out->addLong(code);
			out->addLong(dataSize); 
			out->addLong(wc->Data);
			out->addShort(wc->x);
			out->addShort(wc->y);
			break;
		}
	case NETWORK_UPDATE_PLAYER_EQUIPS:
		{
			Item_Equippable** wornEquipment = (Item_Equippable**)data;
			// serialize this player's list of equipped items
			ByteBuffer* bb = new ByteBuffer;
			int clientIndex = m_net->getIndex();
			if(clientIndex == -1) clientIndex++;
			bb->addShort((short)clientIndex);
			bb->addShort(dataSize);
			for(int i = 0; i < dataSize; ++i){
				if(wornEquipment[i] == NULL)
					bb->addShort(0);
				else{
					bb->addShort(1);
					wornEquipment[i]->serialize(bb);
				}
			}

			out->addLong(NETWORK_UPDATE_PLAYER_EQUIPS);
			out->addLong(bb->size());
			out->addBytes(bb->getRawList(), bb->size());
			delete bb;
			bb = NULL;
			break;
		}
	case NETWORK_SEND_PROJECTILE:
		{
			Item_Projectile* p = (Item_Projectile*)data;
			ByteBuffer* serializedData = new ByteBuffer;
			p->serialize(serializedData);
			out->addLong(code);
			out->addLong(serializedData->size());
			out->addBytes(serializedData->getRawList(), serializedData->size());

			delete serializedData;
			break;
		}
	case NETWORK_UPDATE_ENTITY_HEALTH:
		{
			short* formattedData = (short*)data;
			out->addLong(code);
			out->addLong(dataSize);
			for(unsigned i = 0; i < (dataSize / sizeof(short)); ++i)
				out->addShort(formattedData[i]);
			break;
		}
	}
	

	return NETWORK_SUCCESS;
}

// TODO: Make this not hardcoded for player updating position
void Networking::removeDuplicateData(ByteBuffer* buffer, void* data)
{
	int outCode;
	int outSize;
	int start;
	int end;
	MoveInfo outMove;
	MoveInfo* m = (MoveInfo*) data;

	// check to see if there is already a message concerning that player
	for(int i = 0; i < buffer->size();)
	{
		start = i;
		outCode = buffer->getLong(i);
		i += sizeof(long);
		outSize = buffer->getLong(i);
		i += sizeof(long);
		end = i + outSize;
		if(outCode != NETWORK_UPDATE_ENTITY)
			i = end;	// if it's not an update message, just skip to the end of the message
		else
		{
			// realized that outData isn't even used
			//for(int j = i; j < outSize; ++j)
			//	outData.addByte(buffer->getByte(j));
			outMove.index = buffer->getShort(i);

			// if the index is a match, remove that entire message
			if(m->index == outMove.index)
			{
				for(int j = 0; j < end-start; ++j)
					buffer->remove(start);
				i = start;
			}
			// if not, skip to the end of the message
			else
				i = end;
		}
	}
}